Reading and Writing Your Part
In OpenDoc, reading an object (also called internalization) is the process of initializing (or re-initializing) the object by bringing its stored data into memory from persistent storage. Conversely, writing (also called externalization) is the process of copying an object's essential data to persistent external storage. In general, when a document is opened, its objects are read into memory. As the user edits the document, the objects are modified. When the user saves the document, the modified objects are written back to storage. (As noted earlier, the container suite controls how data is physically stored; reading and writing may not necessarily involve an immediate transfer of data between memory and an external physical storage medium.)This section discusses how your part reads and writes its own content data, as well as related OpenDoc objects, such as frames, that it uses.
Your part editor must be able to read any parts whose data formats (part kinds) it recognizes. You are not required to read the entire contents of your part into memory at once, and you needn't write it all to storage at once. However, reading must put your part in a state in which it can accept events and handle requests, and writing it must ensure that changes made by the user are not lost.
Your part editor should never change the part kind of any part you handle except as a result of an explicit user request. Any translations required to make a part readable, whether performed by OpenDoc or by your part editor, are first selected by the user. See "Binding" for more information.
Initializing and Reading a Part From Storage
Any part must be able to read itself (reconstruct itself in memory by reading its stored data). To maximize performance, OpenDoc requires reading and writing only when absolutely necessary. A part is not instantiated and read into memory until its draft'sCreatePart
orAcquirePart
method has been called--meaning that the part editor is needed for tasks such as drawing, editing, frame negotiation, or script execution. When a part is read in, OpenDoc has already bound it to a part editor according to its part kind (and loaded the part editor if it is not already in memory).Whenever a document in which your part is visible is opened, or whenever your part is added to a document, your part's data must be read into memory. Note that OpenDoc parts should always be ready to work from an empty storage unit as well. The two fundamental methods that your part must implement for initializing itself are
InitPart
andInitPartFromStorage
.The somInit Method
When your part object is first created (or recreated from storage), and before receiving a call to itsInitPart
orInitPartFromStorage
method, your part receives a call to its System Object Model (SOM) object constructorsomInit
. ThesomInit
method is inherited from thesomObject
class of SOM; when you subclassODPart
, you must overridesomInit
.Your
somInit
method should initialize (clear) your part object's instance variables. You must not perform any tasks in this method that might fail. You can set pointer variables to null and assign appropriate values to numeric variables. If you have any initialization code that can potentially fail, your part'sInitPart
orInitPartFromStorage
method must handle it.The InitPart Method
TheInitPart
method is called only once in your part's lifetime, by your draft'sCreatePart
method, when your part is first created and has no previously stored data to read. (If your part is created from stationery, this method is never called.) This is its interface:
void InitPart(in ODStorageUnit storageUnit, in ODPart partWrapper);One possible approach toInitPart
is to take steps such as these:
At this point you might mark your part as clean (unchanged). You could use a "dirty flag" for this purpose, which you initialize as cleared, and then set whenever you change your part's content.
- Call your inherited
InitPart
method, which in turn calls its inheritedInitPersistentObject
method to initialize the information your part needs as a persistent object. (Your classes' initialization methods should always call their superclasses' initialization methods.)- If your part stores its data in multiple part kinds, add a contents property (type
kODPropContents
) and a value (of your highest-fidelity part kind) to the storage unit passed to you, thus preparing your part for eventual writing of its data. Add values for other part kinds also, if appropriate. Initialize the values if necessary.- If your part stores more than one part kind, add a preferred-kind property (type
kODPropPreferredKind
) to the storage unit passed to you. Write into a value of that property an ISO string representing your editor's highest-
fidelity part kind. Normally, that part kind should equal the type of the first value in your contents property.- Save, in a field (such as
fSelf
) of your part, the contents of thepartWrapper
parameter passed to this method. It represents an object reference through which OpenDoc interacts with your part. See "The Part-Wrapper Object" for more information.- Initialize any data pointers or size values that your part maintains.
- Note
- Another approach to
InitPart
is to defer all writing (such as that in steps 2 and 3) until your part'sExternalize
method is called.![]()
The InitPartFromStorage Method
TheInitPartFromStorage
method is similar toInitPart
, except thatInitPartFromStorage
also reads in data. Your draft'sAcquirePart
method calls yourInitPartFromStorage
method and supplies a storage unit from which the part reads itself. Your part retrieves, from thekODPropContents
property of the storage unit, the value (specified by part kind) that represents the data stream you wish to read. This is the interface toInitPartFromStorage
:
void InitPartFromStorage(in ODStorageUnit storage unit, in ODPart partWrapper);In yourInitPartFromStorage
method, take steps like these:
- Call your inherited
InitPartFromStorage
method. That method calls its inheritedInitPersistentObject
method, which initializes the persistent-object information previously stored for your part.- Determine the appropriate part kind of data to read in.
- Your editor may be reading in a part that was created or previously edited by a different editor (see "The Binding Process"kODPropPreferredKind of the storage unit passed to you, and retrieve the part's preferred kind (the part kind you should read, if possible). This part kind may or may not equal the type of the first value in the part's contents property.
- If the
kODPropPreferredKind
property does not exist or if you cannot read data of that part kind, find the highest-fidelity (earliest in storage order) part kind that you can read.(If there is no preferred kind, take the kind that you can read to be the preferred kind. Use that preferred-kind designation later, when you write to storage.)
If the data is non-OpenDoc file data to which your part editor has just been bound because of a drop operation (see "Accepting Non-OpenDoc Data"), you can take the following steps (on the Mac OS platform). Otherwise, go on to step 3.
- Find, in the contents property, a value type representing the file type. If the item is a text file, for example, OpenDoc will have provided the value type (OpenDoc ISO prefix followed by) "MacOS:OSType:FileType:TEXT".
- Assuming your part can read data of that file type, you can then obtain the file's
HFSFlavor
structure (defined by the Mac OS Drag Manager) from a value of type (ISO prefix plus) "MacOS:OSType:ScrapType:hfs ", also provided in the contents property by OpenDoc.- Using information in the
HFSFlavor
structure (such as the file specification), you can then make file-system-specific calls to open the file and process its contents into a buffer in memory. If you must store the data immediately and the draft permissions allow you to do so, write the data into an appropriate value or values in your part's contents property. Otherwise, you can wait until yourExternalize
method is called before writing it to your storage unit.Skip to step 7 (you have already read the data into your part).
- Focus the storage unit on its contents property and on the value containing the part's preferred kind, if you can read that kind. If you cannot read the preferred kind, focus on the highest-fidelity part kind that you can read. (Even if you do not read in the preferred kind at this stage, do not yet change the value in the
kODPropPreferredKind
property.)- Read the data into your part's buffer, using the
GetValue
method.- Read in additional objects as needed--that is, as persistent references within your main storage unit's
kODPropContents
property lead you to information stored elsewhere. See, for example, "Creating Additional Storage Units".- If your part's display frames have been stored, read them in as discussed in "Storing and Retrieving Display Frames".
- Call your draft's
GetPermissions
method and save the results in a field of your part. Respect the permissions in later attempts to write to the draft. See "Drafts"- Save, in a field (such as
fSelf
) of your part, the contents of the partWrapper parameter passed toInitPartFromStorage
. The parameter represents a reference (pointer) through which OpenDoc calls back to your part. See "The Part-Wrapper Object" for more information.- Initialize any data pointers or size values that your part maintains. At this time you might, if you maintain a dirty flag, mark your part as clean (unchanged).
Writing a Part to Storage
Your part must be able to write itself (write its data to persistent storage, typically external storage such as a disk) when instructed to do so. This instruction may come at any time, not just when the user saves or closes your document. To maximize performance, OpenDoc requires writing only when absolutely necessary.
Your part editor can follow these policies strictly, or it can deviate from them as necessary. For example, you can write out your part at any time, without waiting for the user to save the entire document. Note, however, that no matter how many times your part writes its data to storage, none of those changes will become persistent unless the user performs a save.
- A part is not written to storage until the user performs a save operation on its document, causing the part's
Externalize
method to be called. If the user does not perform a save operation on a document, no changes to any of its parts are permanently recorded.- When its document is saved or closed, no writing is required of a part if it has not been altered since it was read into memory.
This section discusses the
Externalize
andExternalizeKinds
methods of your part editor. Another method that involves writing your part data to storage is theCloneInto
method, described in the section "The CloneInto Method of Your Part Editor".The Externalize Method
A caller instructs your part to write its data to storage by calling your part's override of its inheritedExternalize
method. Your part then writes its data to its storage unit.As a minimum, your part must write one property:
kODPropContents
. ThekODPropContents
property contains your part's intrinsic data. You can write your part's data in multiple part kinds, representing multiple data formats, in thekODPropContents
property. Each format is a separate stream, defined as a separate value in the property, and each value must be a complete representation of your part's contents.You can add other properties and values as annotations to your part's storage unit, and you can access them for your own purposes. However, remember that during the binding process, OpenDoc considers only the value types of
kODPropContents
when assigning an editor to a part.The fundamental method that your part must override for writing its data to storage is the
Externalize
method, inherited from the classODPersistentObject
. This is its interface:
void Externalize();In a simpleExternalize
method, you might take these basic steps:
Typically, in writing your content you might write out just two values: one in the preferred part kind, and another in a standard, widely recognized part kind useful for data interchange. If your part editor supports many kinds, you might allow the user to select a single default kind, to avoid the creation of very large files full of redundant data.
- Call the inherited
Externalize
method, to make sure that the appropriate persistent-object information for your part is written to storage.- Examine your part to see whether it is dirty--that is, whether the content has been changed since the last write. If it is clean (unchanged), take no further action.
- Get a reference to your main storage unit by calling your part's inherited
GetStorageUnit
method.- Your editor may be writing a part that was created or previously edited by a different editor (see "The Binding Process"). If so, you need to prepare the contents property of the part to match the part kinds and fidelity order that your part editor uses. (This step is required only the first time you write a part after your editor has been first bound to it.)
- Remove any values in the storage unit that represent kinds that your part editor does not support or does not intend to save.
- Add values if necessary, so that values for all part kinds that your part editor intends to include are present. Make sure the values are in fidelity order, and make sure that the preferred part kind, determined when you read the part in, is one of the values that you write.
- Focus the storage unit on the contents property and on each of the part kinds of the data you are writing in turn. Write your data to the storage unit, using its
SetValue
method. Write one value for each part kind of data you store.If your part is a container part, you write persistent references to your embedded frames as part of writing your content. See "Storing and Retrieving Embedded Frames".
- Store references to your display frames, as described in "Storing and Retrieving Display Frames".
- Mark your part as clean (unchanged).
Your part can write its contents to its storage unit at any time, not just in response to a call to its
- IMPORTANT
- Do not change the preferred kind that was specified when you read the part, even if you can easily convert the data to a higher-fidelity part kind. (You can, however, store a higher-fidelity part kind in addition to the preferred kind, if you wish.) Changing the preferred kind is implicit translation, and translation should always be controlled by the user. Maintain the preferred kind until the user instructs you to change it, as in the examples described in "Binding With Translation".
![]()
Externalize
method. However, changes to any parts in a document are actually made persistent only when the user performs an explicit save, and thus only whenExternalize
is called. If your part updates its storage unit but the user never saves the document, your changes are lost.The ExternalizeKinds Method
Your part'sExternalizeKinds
method can be called whenever your part is expected to save its data with a specific set of formats. For example, OpenDoc callsExternalizeKinds
when the user saves a copy of your part's document in multiple formats. A document-interchange utility or service might call theExternalizeKinds
method of all parts in a document to create a version of the document in which all data is written in one or more common standard part kinds.When your part's
ExternalizeKinds
method is called, it is passed a list of part kinds. This is the method's interface:
void ExternalizeKinds(in ODTypeList kindSet);The method should write as many of the specified part kinds as it supports, as well as your part's preferred kind. The method should ignore part kinds on the list that it does not support, and it should remove values (other than the preferred kind) from its storage unit that are not on the list. Just likeExternalize
, theExternalizeKinds
method should always write the part kinds in proper fidelity order, and it should not change the preferred kind.Creating Additional Storage Units
Your part can use persistent references to create an auxiliary storage unit for keeping additional data. The auxiliary unit must be referenced from your part's main storage unit, using a strong persistent reference. In brief, you can follow these steps:
Your part can later retrieve the auxiliary storage unit from the main storage unit, in this way:
- Call the
CreateStorageUnit
method of your part's draft to create the storage unit to hold the data.- Focus your part's main storage unit on the value in your contents property that is to contain the persistent reference. (It is not necessary to create a separate value to hold the reference.)
- Create the persistent reference by calling the
GetStrongStorageUnitRef
method of your main storage unit.- Store the reference anywhere in the value; the only requirement is that you be able to recognize and retrieve the reference later. Write the value to persistent storage by calling the
SetValue
method of your main storage unit.
How you store the data in your auxiliary storage unit is completely up to you. You can define your own properties, or you can keep the kinds of properties used in your main storage unit. If you define your own property names, avoid using constants for them that start with
- Focus the main storage unit on the value that contains the persistent reference.
- Read the value, using the
GetValue
method of your main storage unit. Retrieve the persistent reference from the value's data.- Get the storage-unit ID of your auxiliary storage unit from the persistent reference by calling the
GetIDFromStorageUnitRef
method of your main storage unit.- Get the storage unit itself by passing its ID to the
AcquireStorageUnit
method of your draft.
kOD
; only OpenDoc-defined constants should use those characters.Storing and Retrieving Display Frames
As noted in the section "Working With Your Display Frames and Facets").If your part object includes a field that is a list of display-frame objects, your
InitPart
method can first create the field, and yourDisplayFrameAdded
,DisplayFrameRemoved
,DisplayFrameConnected
, andDisplayFrameClosed
methods can add or delete elements of the list, as appropriate.In this list you can keep additional information about the frames, such as what portion of your content (like a page number) each displays. That way, you can create only those frames that you currently need to manipulate, using the techniques described in "Lazy Internalization".
You should store your list of frames in an annotation property in your part's storage unit. Your
Externalize
method should create a property with the namekODPropDisplayFrames
in your storage unit and should write the frame list as weak storage unit references into a value of that property.When reading your part from storage, your
InitPartFromStorage
method can focus on thekODPropDisplayFrames
property and value, and read the list of stored display frames back into your display-frame field. (Be sure yourReleaseAll
method releases any remaining display frames in that field; see "The ReleaseAll Method"Storing and Retrieving Embedded Frames
Your part does not explicitly store the data of embedded parts; their own part editors take care of that. You do not even explicitly store the frame objects that you use; OpenDoc takes care of that. You do, however, store persistent references to the frames that are embedded in your part.The process of storing an embedded frame for your part is simple. All you need to do is store a strong persistent reference to the embedded frame object; OpenDoc takes care of storing the frame itself. You follow the same steps that you take when creating and storing a reference to any storage unit (described in "Creating Additional Storage Units"):
When your part is reinstantiated at a later time, your part can then retrieve the frame this way:
- Focus your storage unit on your contents property and on the value that is to contain the persistent reference to the embedded frame.
- Create the persistent reference and place it in your contents data.
- Write the data back into the value.
For efficient memory use, you can create only those embedded frames that you currently need to manipulate or display, using the techniques described in "Lazy Internalization".
- Focus the storage unit on the value containing the persistent reference.
- Retrieve the reference.
- Get the storage-unit ID of the frame.
- Recreate the frame itself, by calling your draft's
AcquireFrame
method.- If you have previously stored information regarding a change to the frame's link status (see "The LinkStatusChanged Method of Your Part Editor".
Reading and Writing Part Info
As noted in the section "Part Info", you can use the part info data of a frame or facet to store any display-related information you want to associate with that particular frame or facet. Because frames are persistent objects and facets are not, a frame's part info can be stored persistently, whereas a facet's part info cannot.Just as you store multiple representations of your part's data, you should store multiple representations of your frames' part info, so that other editors can potentially read your part info as well as your part content. In that case, you should give your part info formats names equivalent to the part kinds with which they are associated. For example, if you write part data whose part kind is "SurfCorp:SurfWriter:StyledText", your associated part info data should be written in a value whose type is "SurfCorp:SurfWriter:StyledText:PartInfo".
If you also write your part data in a standard format such as PICT, RTF, or JPEG, you would not write associated part info for those formats, because they have no associated part info format.
As with other changes to the contents of your part's draft, any time you place or modify information in your display frame's part info data, you should follow the procedures listed in "Making Content Changes Known"nonpersistent frame, however, you do not need to make changes to it known.)
Whenever your document is saved, your part's
WritePartInfo
method is called for each of your part's display frames. Here is its interface:
void WritePartInfo(in ODPointer partInfo, in ODStorageUnitView storageUnitView);On receiving this call, you could first examine your part's own part-info dirty flag (if you have defined one) to see if this frame's part info has changed since it was read in from storage. If so, focus on as many values in the provided storage-unit view as is appropriate and write your current part info into them. (Then clear the dirty flag.)Conversely, whenever a display frame of your part is read into memory, your part's
ReadPartInfo
method is called. Here is its interface:
ODPtr ReadPartInfo(in ODFrame frame, in ODStorageUnitView storageUnitView);On receiving this call, you should allocate a structure to hold the part info, focus on the appropriate value in the provided storage-unit view, and read the data into your part-info structure. (Then initialize your part-info dirty flag to clean.)Your part also writes part info data when its display frames are cloned; see "The ClonePartInfo Method of Your Part Editor"
Main | Page One | What's New | Apple Computer, Inc. | Find It | Contact Us | Help